/* 
 * File:   main.c
 * Author: nell, toto, 16, DJ M.
 *
 * Created on May 29, 2012, 3:03 PM
 */

#include "merge.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "types.h"
#include "stringutils.h"
#include "arbrehuffman.h"
#include "listearbre.h"
#include "arbreCanonique.h"
#include "binio.h"
#include "merge.h"
#include "decompression.h"

/* statistique compte le nombre d'appartion de chaque caractère dans un fichier.
 * @param le fichier a lire
 * @return Un pointeur sur tableau d'entier conteant le nombre d'apparition de
 *  chaque caractère dans la chaîne.
 */
int* statistique(FILE* fich) {
    // Alloue un tableau de 256 cases dynamiquement et l'initialise.
    int* tab = (int*) calloc(TAILLE_DICTIONNAIRE, sizeof (int));
    int c;

    while ((c = fgetc(fich)) != EOF) {
        tab[c]++;
    }
    fflush(stdout);

    return tab;
}

/* Trie les statistiques sous forme d'une liste chainée.
 * @param  tab le pointeur sur le tableau de statistique.
 * @return une liste chainée triée selon leurs nombres d'occurence 
 * dans le fichier.
 */
liste* trierListe(int* tab) {
    int i;
    int max = 1, fin = TAILLE_DICTIONNAIRE;
    liste* lis = NULL;

    //Tant qu'il y a des des lettres présente et non traitées.
    while (max != 0) {
        max = 0;
        for (i = 0; i < fin; i++) {
            if (tab[i] > tab[max]) {
                max = i;
            }
        }
        // Ajout du max en tête.
        if (max != 0) {
            insererEnTete(&lis, creerMaillonAbr(stringFromChar(max), tab[max], NULL, NULL, NULL));
            tab[max] = 0;
        }
    }
    return lis;
}

void printListe(liste* list) {
    if (list != NULL) {
        printf("%s - %d\n", list->info->lettre, list->info->val);
        printListe(list->suiv);
    }
}

void printTableau(tableau* t) {
    LetVal * tab = t->tab;
    printf("Nombre: %d\n", t->nombre);
    for (int i = 0; i < t->nombre; i++) {
        printf("%s - %d\n", tab[i].lettre, tab[i].prof);
    }
}

void printbit(int bit) {
    if (bit)
        printf("1");
    else
        printf("0");
}

void printint(long long int code, short int longueur) {
    for (int i = longueur - 1; i >= 0; i--) {
        printbit((code >> i) & 0x1);
    }
    printf("\n");
}

void printCodage(LongCod * codage) {
    for (int i = 0; i < TAILLE_DICTIONNAIRE; i++) {
        if (codage[i].longeur != 0) {
            printf("%c - %d - ", (char) i, codage[i].longeur);
            printint(codage[i].code, codage[i].longeur);
        }
    }
}

/* Ecris le codage utilisé à la suite du fichier sortie.
 * @param sortie le fichier où écrire.
 * @param codage le tableau constituant le codage.
 */
void ecrire_codage(FILE* sortie, LongCod* codage) {
    int nombre = 0;
    for (int i = 0; i < TAILLE_DICTIONNAIRE; i++)
        if (codage[i].longeur != 0)
            nombre++;

    fprintf(sortie, "%d\n", nombre);
    for (int i = 0; i < TAILLE_DICTIONNAIRE; i++)
        if (codage[i].longeur != 0)
            fprintf(sortie, "%c %d\n", (char) i, codage[i].longeur);

    fflush(sortie);
}

/* Ecris dans le fichier le nombre de caractère présent dans le fichier à coder.
 * @ensure le nombre de caractère est écris en tête du fichier.
 * @param le fichier à coder (entrée)
 * @param sortie le fichier où écrire.
 */
void ecrire_taille_fichier(FILE* entree, FILE* sortie) {
    char c;
    int cpt = 0;

    rewind(entree);
    while ((c = fgetc(entree)) != EOF) {
        cpt++;
    }
    fprintf(sortie, "%d\n", cpt);

}

void encoder_fichier(FILE* entree, FILE* sortie, binio_t out, LongCod* codage) {
    ecrire_taille_fichier(entree, sortie);
    rewind(entree);
    printf("avant ecrire codage\n");
    ecrire_codage(sortie, codage);
    printf("apres ecrire codage\n");
    int c;
    while ((c = fgetc(entree)) != EOF) {
        binio_unsigned_write(out, codage[c].code, codage[c].longeur, 0);
        //printint(codage[c].code, codage[c].longeur);
    }

    fflush(sortie);
    binio_close(out);
    printf("fin_encoder_fichier\n");
}

int argumentValid(int argc, char** argv) {
    if (argc != 4 && ! (argc >= 2 && argv[1][0] == 'v'))
        return 0;

    if (strcmp(argv[1], "c") != 0 && strcmp(argv[1], "v") != 0 && strcmp(argv[1], "ch") != 0 && strcmp(argv[1], "cp") != 0 && strcmp(argv[1], "x") != 0)
        return 0;

    return 1;
}

void setOptions(char* arg, int* compress, int* expand, int* methode, int* version) {
    if (strchr(arg, 'c') != NULL)
        *compress = 1;

    if (strchr(arg, 'h') != NULL)
        *methode = ENC_HUFF;

    if (strchr(arg, 'x') != NULL)
        *expand = 1;

    if (strchr(arg, 'p') != NULL)
        *methode = ENC_PACKAGE;

    if (strchr(arg, 'v') != NULL)
        *version = 1;

}

int compressAction(FILE* entree, FILE* sortie, int methode) {
    int* tab = statistique(entree);

    liste* list = trierListe(tab);
    printListe(list);
    tableau*t;

    if (methode == ENC_HUFF) {
        arbre* huffman = construireHuffman(&list);
        printarbre(huffman, 0);
        t = tableLongueur(huffman);
    } else if (methode == ENC_PACKAGE) {
        t = packageMerge(list, HAUTEUR_ARBRE_MAX);
        triBulles(t);
    } else {
        return EXIT_FAILURE;
    }

    printTableau(t);
    printf("\n\nCODAGE\n");
    LongCod* codage = creerCodage(t);

    printCodage(codage);

    binio_init(2);

    binio_t out = binio_write_open((binio_fputc_t *) fputc,
            (binio_eof_t *) feof,
            (binio_file_func_param_t) sortie,
            1);

    printf("avant encoder fichier\n");

    encoder_fichier(entree, sortie, out, codage);

    return EXIT_SUCCESS;
}

int expandAction(FILE* entree, FILE* sortie) {

    decoder_fichier(entree, sortie);
    return EXIT_SUCCESS;
}

/**
 * prog c file out
 * prog ch file out
 * prog x file out
 * 
 */
int main(int argc, char** argv) {
    int version = 0;
    int compress = 0;
    int expand = 0;
    int methode = ENC_HUFF;

    if (!argumentValid(argc, argv)) {
        printf("Usage: COMMANDE c/ch/cp/x fichier_entree fichier_sortie\n");
        return EXIT_SUCCESS;
    }

    setOptions(argv[1], &compress, &expand, &methode, &version);

    if(version){
        printf("1.0\n");
        exit(0);
    }
    
    FILE* entree = fopen(argv[2], "r");
    if (entree == NULL) {
        printf("Fichier d'entree (%s) inexistant\n", argv[2]);
        return EXIT_FAILURE;
    }

    FILE* sortie = fopen(argv[3], "w");
    if (sortie == NULL) {
        printf("Erreur de creation du fichier de sortie %s", argv[3]);
        return EXIT_FAILURE;
    }

    if (compress) {
        return compressAction(entree, sortie, methode);
    } else if (expand) {
        return expandAction(entree, sortie);
    }
}

